﻿// 2020/7/1: 首个版本
// 2020/8/3: 修正禁停区解析bug
// 2020/11/6: 增加停车位是否被占用字段
// 2021/7/7: 新增trafficSampleProtocols
// 2023/9/12: StopSign改为TrafficSign，并增加TrafficSignType
// 2023/10/11: 补充部分英文注释
// 2024/3/25: 修正注释
// 2024/5/20: 新增TrafficLight.stopPosX/Y
// 2025/4/30: 增加trafficSampleConverters

var TrafficElementType = {
    Invalid : 0,
    SpeedLimit : 1, // 限速标志
    TrafficSign : 2, // 交通标志
    TrafficLight : 3, // 交通信号灯
    StopLine : 4, // 停止线
    SpeedBump : 5, // 减速带
    ArrowMark : 6, // 路面箭头
    ProhibitionZone : 7, // 禁停区
    ParkingSlot : 8, // 停车位
    ZebraCrossing : 9, // 斑马线
};

var TrafficSignType = {
    Stop : 0, // 停止标志

    NoEntry : 1, // 禁止驶入
    NoDriving : 2, // 禁止通行
    NoParking : 3, // 禁止停车
    NoLongTimeParking : 4, // 禁止长时间停车
    NoVehicle : 5, // 禁止机动车驶入
    NoTruck : 6, // 禁止货车驶入
    NoBus : 7, // 禁止客车驶入
    NoPedestrian : 8, // 禁止行人进入
    NoLeftTurn : 9, // 禁止左转
    NoRightTurn : 10, // 禁止右转
    NoAnyTurn : 11, // 禁止转弯
    NoForward : 12, // 禁止直行
    NoForwardOrLeft : 13, // 禁止直行或左转
    NoForwardOrRight : 14, // 禁止直行或右转
    NoUTurn : 15, // 禁止调头
    NoHorn : 16, // 禁止鸣笛
    NoOvertake : 17, // 禁止超车
    NoOvertakeEnder : 18, // 禁止超车区间结束
    NoDangerousGoods : 19, // 禁止危险品车辆驶入

    LowSpeedLimit : 50, // 低速限速
    SpeedLimitEnder : 51, // 限速区间结束
    WeightLimitWheel : 52, // 轴重限重
    WeightLimitTotal : 53, // 全车限重
    HeightLimit : 54, // 高度限制
    WidthLimit : 55, // 宽度限制
    Yield : 56, // 让行标志
    StopAndCheck : 57, // 停车检查

    GeneralSign : 100, // 指示类标志
    GeneralNoSpecialVehicle : 101, // 禁止特种车辆驶入
    GeneralConstruction : 102, // 施工标志
    GeneralWarning : 103, // 警告牌
    GeneralGreenBackground : 104, // 绿底指示牌
    GeneralBlueBackground : 105, // 蓝底指示牌
    GeneralRedBackground : 106, // 红底指示牌
    GeneralWhiteBackground : 107, // 白底指示牌
    GeneralYellowRoadID : 108, // 黄底道路ID牌
    GeneralRedRoadID : 109, // 红底道路ID牌
};


var TrafficLightState = {
    Invalid : 0,
    Stop : 1, // 红灯
    Continue : 2, // 黄灯
    Caution : 3, // 黄灯闪烁
    Allow : 4, // 绿灯
    AllowFlash : 5, // 绿灯闪烁
    StopFlash : 6, // 红灯闪烁
};

var ParkingSlotOccupancy = {
    Unknown: 0, // 未知
    NotOccupied: 1, // 未被占用
    Occupied: 2, // 被占用
};

function ArrowCombination(valid) {
    this.valid = valid; // false while direction is not specified / 是否有效，若不分方向(如实心圆交通灯信号)则为false
    this.goStraight = false; // 直行
    this.turnLeft = false; // 左转
    this.turnRight = false; // 右转
    this.uTurn = false; // 调头
}

function SpeedLimit(id, rawID) {
    this.type = TrafficElementType.SpeedLimit;
    this.id = id; // 0 as invalid / 交通元素ID，0表示无效
    this.rawID = rawID; // 交通元素的原始ID
    this.limitSpeed = 0; // [KPH] 限速
    this.posX = null; // [m] 纵向位置
    this.posY = null; // [m] 横向位置
    this.posZ = null; // [m] 垂直位置
}

function TrafficSign(id, rawID, signType) {
    this.type = TrafficElementType.TrafficSign;
    this.id = id; // 0 as invalid / 交通元素ID，0表示无效
    this.rawID = rawID; // 交通元素的原始ID
    this.signType = signType; // 交通标志类型
    this.posX = null; // [m] 纵向位置
    this.posY = null; // [m] 横向位置
    this.posZ = null; // [m] 垂直位置
}

function TrafficLight(id, rawID) {
    this.type = TrafficElementType.TrafficLight;
    this.id = id; // 0 as invalid / 交通元素ID，0表示无效
    this.rawID = rawID; // 交通元素的原始ID
    this.state = TrafficLightState.Invalid; // 信号灯状态
    this.arrows = new ArrowCombination(false); // false while direction is not specified / 是否有效，若不分方向(如实心圆交通灯信号)则为false
    this.posX = null; // [m] 纵向位置
    this.posY = null; // [m] 横向位置
    this.posZ = null; // [m] 垂直位置
    this.stopPosX = null; // [m] 纵向停止位置
    this.stopPosY = null; // [m] 横向停止位置
}

function StopLine(id, rawID) {
    this.type = TrafficElementType.StopLine;
    this.id = id; // 0 as invalid / 交通元素ID，0表示无效
    this.rawID = rawID; // 交通元素的原始ID
    this.distance = 0.0; // [m] 停止线与x轴相交位置
    this.heading = null; // [°] 停止线法线朝向，逆时针为正方向
}

function SpeedBump(id, rawID) {
    this.type = TrafficElementType.SpeedBump;
    this.id = id; // 0 as invalid / 交通元素ID，0表示无效
    this.rawID = rawID; // 交通元素的原始ID
    this.distance = 0.0; // [m] 减速带与x轴相交位置
    this.heading = null; // [°] 减速带法线朝向，逆时针为正方向
}

function ArrowMark(id, rawID) {
    this.type = TrafficElementType.ArrowMark;
    this.id = id; // 0 as invalid / 交通元素ID，0表示无效
    this.rawID = rawID; // 交通元素的原始ID
    this.arrows = new ArrowCombination(true); // 路面标志的箭头组合
    this.posX = null; // [m] 纵向位置
    this.posY = null; // [m] 横向位置
}

function ProhibitionZone(id, rawID) {
    this.type = TrafficElementType.ProhibitionZone;
    this.id = id; // 0 as invalid / 交通元素ID，0表示无效
    this.rawID = rawID; // 交通元素的原始ID
    this.centerX = 0.0; // [m] 禁停区中央的纵向位置
    this.centerY = 0.0; // [m] 禁停区中央的横向位置
    this.width = 0.0; // [m] 禁停区的宽度
    this.length = 0.0; // [m] 禁停区的长度
    this.heading = null; // [°] 禁停区的朝向(沿长度方向)，逆时针为正方向
}

function ParkingSlot(id, rawID) {
    this.type = TrafficElementType.ParkingSlot;
    this.id = id; // 0 as invalid / 交通元素ID，0表示无效
    this.rawID = rawID; // 交通元素的原始ID
    this.centerX = 0.0; // [m] 停车位中央的纵向位置
    this.centerY = 0.0; // [m] 停车位中央的横向位置
    this.width = 0.0; // [m] 停车位的宽度
    this.length = 0.0; // [m] 停车位的长度
    this.heading = null; // [°] 停车位的朝向(沿长度方向)，逆时针为正方向
    this.occupancy = ParkingSlotOccupancy.Unknown; // 停车位是否被占用
}

function ZebraCrossing(id, rawID) {
    this.type = TrafficElementType.ZebraCrossing;
    this.id = id; // 0 as invalid / 交通元素ID，0表示无效
    this.rawID = rawID; // 交通元素的原始ID
    this.nearDistance = 0.0; // [m] 斑马线近端与x轴相交位置
    this.farDistance = null; // [m] 斑马线远端与x轴相交位置
    this.heading = null; // [°] 斑马线的朝向，逆时针为正方向
}

function TrafficSample(session, time) {
    this.session = session; // Date or AppoxSessionIdentifier
    this.time = time; // [s] Time offset in session / Session内的相对时间
    this.elements = []; // List of traffic elements / 交通元素列表
}

function convTrafficSampleV1(gs) {
    if (gs.values.length < 1) return null;

    var count = gs.values[0] == null ? 0 : gs.values[0];
    if (gs.values.length < 1 + 3 * count) return null;

    if (count > 0) {
        var lastOffset = gs.values[3 * count - 1];
        var lastLength = gs.values[3 * count];
        var totalDataSize = lastOffset + lastLength;
        if (gs.values.length < 1 + 3 * count + totalDataSize) return null;
    }

    var sample = new TrafficSample(gs.session, gs.time);
    for (var i = 0; i < count; i++) {
        if (gs.values[1 + 3 * i] == null || gs.values[2 + 3 * i] == null) continue;

        var type = gs.values[1 + 3 * i];
        if (type == TrafficElementType.Invalid) continue;

        var bas = 1 + 3 * count + gs.values[2 + 3 * i];
        switch (type) {
            case TrafficElementType.SpeedLimit:
                if (gs.values[bas] != null) {
                    var speedLimit = new SpeedLimit(0, null);
                    speedLimit.limitSpeed = gs.values[bas];
                    speedLimit.posX = gs.values[bas + 1];
                    speedLimit.posY = gs.values[bas + 2];
                    sample.elements.push(speedLimit);
                }
                break;
            case TrafficElementType.TrafficSign:
                {
                    var trafficSign = new TrafficSign(0, null, TrafficSignType.Stop);
                    trafficSign.posX = gs.values[bas];
                    trafficSign.posY = gs.values[bas + 1];
                    sample.elements.push(trafficSign);
                }
                break;
            case TrafficElementType.TrafficLight:
                if (gs.values[bas] != null) {
                    var trafficLight = new TrafficLight(0, null);
                    trafficLight.state = gs.values[bas];
                    trafficLight.posX = gs.values[bas + 1];
                    trafficLight.posY = gs.values[bas + 2];
                    if (gs.values[bas + 3] != null && gs.values[bas + 4] != null && gs.values[bas + 5] != null && gs.values[bas + 6] != null) {
                        trafficLight.arrows.valid = true;
                        trafficLight.arrows.goStraight = gs.values[bas + 3] == 1;
                        trafficLight.arrows.turnLeft = gs.values[bas + 4] == 1;
                        trafficLight.arrows.turnRight = gs.values[bas + 5] == 1;
                        trafficLight.arrows.uTurn = gs.values[bas + 6] == 1;
                    }
                    sample.elements.push(trafficLight);
                }
                break;
            case TrafficElementType.StopLine:
                if (gs.values[bas] != null) {
                    var stopLine = new StopLine(0, null);
                    stopLine.distance = gs.values[bas];
                    stopLine.heading = gs.values[bas + 1];
                    sample.elements.push(stopLine);
                }
                break;
            case TrafficElementType.SpeedBump:
                if (gs.values[bas] != null) {
                    var speedBump = new SpeedBump(0, null);
                    speedBump.distance = gs.values[bas];
                    speedBump.heading = gs.values[bas + 1];
                    sample.elements.push(speedBump);
                }
                break;
            case TrafficElementType.ArrowMark:
                if (gs.values[bas] != null && gs.values[bas + 1] != null && gs.values[bas + 2] != null && gs.values[bas + 3] != null) {
                    var arrowMark = new ArrowMark(0, null);
                    arrowMark.arrows.goStraight = gs.values[bas] == 1;
                    arrowMark.arrows.turnLeft = gs.values[bas + 1] == 1;
                    arrowMark.arrows.turnRight = gs.values[bas + 2] == 1;
                    arrowMark.arrows.uTurn = gs.values[bas + 3] == 1;
                    arrowMark.posX = gs.values[bas + 4];
                    arrowMark.posY = gs.values[bas + 5];
                    sample.elements.push(arrowMark);
                }
                break;
            case TrafficElementType.ProhibitionZone:
                if (gs.values[bas] != null && gs.values[bas + 1] != null && gs.values[bas + 2] != null && gs.values[bas + 3] != null) {
                    var prohibitionZone = new ProhibitionZone(0, null);
                    prohibitionZone.centerX = gs.values[bas];
                    prohibitionZone.centerY = gs.values[bas + 1];
                    prohibitionZone.width = gs.values[bas + 2];
                    prohibitionZone.length = gs.values[bas + 3];
                    prohibitionZone.heading = gs.values[bas + 4];
                    sample.elements.push(prohibitionZone);
                }
                break;
            case TrafficElementType.ParkingSlot:
                if (gs.values[bas] != null && gs.values[bas + 1] != null && gs.values[bas + 2] != null && gs.values[bas + 3] != null) {
                    var parkingSlot = new ParkingSlot(0, null);
                    parkingSlot.centerX = gs.values[bas];
                    parkingSlot.centerY = gs.values[bas + 1];
                    parkingSlot.width = gs.values[bas + 2];
                    parkingSlot.length = gs.values[bas + 3];
                    parkingSlot.heading = gs.values[bas + 4];
                    sample.elements.push(parkingSlot);
                }
                break;
            case TrafficElementType.ZebraCrossing:
                if (gs.values[bas] != null) {
                    var zebraCrossing = new ZebraCrossing(0, null);
                    zebraCrossing.nearDistance = gs.values[bas];
                    zebraCrossing.farDistance = gs.values[bas + 1];
                    zebraCrossing.heading = gs.values[bas + 2];
                    sample.elements.push(zebraCrossing);
                }
                break;
        }
    }

    return sample;
}

function convTrafficSampleV2(gs) {
    if (gs.values.length < 1) return null;

    var count = gs.values[0] == null ? 0 : gs.values[0];
    if (gs.values.length < 1 + 6 * count) return null;

    if (count > 0) {
        var lastOffset = gs.values[1 + 6 * (count - 1)];
        var lastLength = gs.values[2 + 6 * (count - 1)];
        var totalDataSize = lastOffset + lastLength;
        if (gs.values.length < 1 + 6 * count + totalDataSize) return null;
    }

    var sample = new TrafficSample(gs.session, gs.time);
    for (var i = 0; i < count; i++) {
        if (gs.values[1 + 6 * i] == null || gs.values[2 + 6 * i] == null || gs.values[3 + 6 * i] == null || gs.values[4 + 6 * i] == null) continue;

        var type = gs.values[3 + 6 * i];
        if (type == TrafficElementType.Invalid) continue;

        var bas = 1 + 6 * count + gs.values[1 + 6 * i];
        var len = gs.values[2 + 6 * i];
        var id = gs.values[4 + 6 * i];
        var rawID = gs.values[5 + 6 * i];

        switch (type) {
            case TrafficElementType.SpeedLimit:
                if (len != 6) break;
                if (gs.values[bas] != null) {
                    var speedLimit = new SpeedLimit(id, rawID);
                    speedLimit.limitSpeed = gs.values[bas];
                    speedLimit.posX = gs.values[bas + 1];
                    speedLimit.posY = gs.values[bas + 2];
                    speedLimit.posZ = gs.values[bas + 3];
                    sample.elements.push(speedLimit);
                }
                break;
            case TrafficElementType.TrafficSign:
                if (len != 5) break;
                {
                    var trafficSign = new TrafficSign(id, rawID, TrafficSignType.Stop);
                    trafficSign.posX = gs.values[bas];
                    trafficSign.posY = gs.values[bas + 1];
                    trafficSign.posZ = gs.values[bas + 2];
                    if (gs.values[bas + 3] != null) {
                        trafficSign.signType = gs.values[bas + 3];
                    }
                    sample.elements.push(trafficSign);
                }
                break;
            case TrafficElementType.TrafficLight:
                if (len != 10) break;
                if (gs.values[bas] != null) {
                    var trafficLight = new TrafficLight(id, rawID);
                    trafficLight.state = gs.values[bas];
                    trafficLight.posX = gs.values[bas + 1];
                    trafficLight.posY = gs.values[bas + 2];
                    trafficLight.posZ = gs.values[bas + 3];
                    if (gs.values[bas + 4] != null && gs.values[bas + 5] != null && gs.values[bas + 6] != null && gs.values[bas + 7] != null) {
                        trafficLight.arrows.valid = true;
                        trafficLight.arrows.goStraight = gs.values[bas + 4] == 1;
                        trafficLight.arrows.turnLeft = gs.values[bas + 5] == 1;
                        trafficLight.arrows.turnRight = gs.values[bas + 6] == 1;
                        trafficLight.arrows.uTurn = gs.values[bas + 7] == 1;
                    }
                    trafficLight.stopPosX = gs.values[bas + 8];
                    trafficLight.stopPosY = gs.values[bas + 9];
                    sample.elements.push(trafficLight);
                }
                break;
            case TrafficElementType.StopLine:
                if (len != 4) break;
                if (gs.values[bas] != null) {
                    var stopLine = new StopLine(id, rawID);
                    stopLine.distance = gs.values[bas];
                    stopLine.heading = gs.values[bas + 1];
                    sample.elements.push(stopLine);
                }
                break;
            case TrafficElementType.SpeedBump:
                if (len != 4) break;
                if (gs.values[bas] != null) {
                    var speedBump = new SpeedBump(id, rawID);
                    speedBump.distance = gs.values[bas];
                    speedBump.heading = gs.values[bas + 1];
                    sample.elements.push(speedBump);
                }
                break;
            case TrafficElementType.ArrowMark:
                if (len != 8) break;
                if (gs.values[bas] != null && gs.values[bas + 1] != null && gs.values[bas + 2] != null && gs.values[bas + 3] != null) {
                    var arrowMark = new ArrowMark(id, rawID);
                    arrowMark.arrows.goStraight = gs.values[bas] == 1;
                    arrowMark.arrows.turnLeft = gs.values[bas + 1] == 1;
                    arrowMark.arrows.turnRight = gs.values[bas + 2] == 1;
                    arrowMark.arrows.uTurn = gs.values[bas + 3] == 1;
                    arrowMark.posX = gs.values[bas + 4];
                    arrowMark.posY = gs.values[bas + 5];
                    sample.elements.push(arrowMark);
                }
                break;
            case TrafficElementType.ProhibitionZone:
                if (len != 7) break;
                if (gs.values[bas] != null && gs.values[bas + 1] != null && gs.values[bas + 2] != null && gs.values[bas + 3] != null) {
                    var prohibitionZone = new ProhibitionZone(id, rawID);
                    prohibitionZone.centerX = gs.values[bas];
                    prohibitionZone.centerY = gs.values[bas + 1];
                    prohibitionZone.width = gs.values[bas + 2];
                    prohibitionZone.length = gs.values[bas + 3];
                    prohibitionZone.heading = gs.values[bas + 4];
                    sample.elements.push(prohibitionZone);
                }
                break;
            case TrafficElementType.ParkingSlot:
                if (len != 7) break;
                if (gs.values[bas] != null && gs.values[bas + 1] != null && gs.values[bas + 2] != null && gs.values[bas + 3] != null) {
                    var parkingSlot = new ParkingSlot(id, rawID);
                    parkingSlot.centerX = gs.values[bas];
                    parkingSlot.centerY = gs.values[bas + 1];
                    parkingSlot.width = gs.values[bas + 2];
                    parkingSlot.length = gs.values[bas + 3];
                    parkingSlot.heading = gs.values[bas + 4];
                    parkingSlot.occupancy = gs.values[bas + 5] == null ? ParkingSlotOccupancy.Unknown : gs.values[bas + 5];
                    sample.elements.push(parkingSlot);
                }
                break;
            case TrafficElementType.ZebraCrossing:
                if (len != 5) break;
                if (gs.values[bas] != null) {
                    var zebraCrossing = new ZebraCrossing(id, rawID);
                    zebraCrossing.nearDistance = gs.values[bas];
                    zebraCrossing.farDistance = gs.values[bas + 1];
                    zebraCrossing.heading = gs.values[bas + 2];
                    sample.elements.push(zebraCrossing);
                }
                break;
        }
    }

    return sample;
}

// Interpolator / 样本插值函数
function interpolateTrafficSample(session, time, s1, w1, s2, w2) {
    var noID = false;
    for (var i = 0; i < s1.elements.length; i++) {
        if (s1.elements[i].id <= 0) {
            noID = true;
            break;
        }
    }
    for (var i = 0; i < s2.elements.length; i++) {
        if (s2.elements[i].id <= 0) {
            noID = true;
            break;
        }
    }

    if (noID) {
        return w1 > w2 ? s1 : s2;
    }

    var sample = new TrafficSample(session, time);
    for (var i1 = 0; i1 < s1.elements.length; i1++) {
        var o1 = s1.elements[i1];
        var id = o1.id;
        var type = o1.type;
        for (var i2 = 0; i2 < s2.elements.length; i2++) {
            var o2 = s2.elements[i2];
            if (o2.id != id || o2.type != type) continue;

            var rawID = w1 > w2 ? o1.rawID : o2.rawID;
            switch (type) {
                case TrafficElementType.SpeedLimit:
                    {
                        var speedLimit = new SpeedLimit(id, rawID);
                        speedLimit.limitSpeed = w1 > w2 ? o1.limitSpeed : o2.limitSpeed;
                        speedLimit.posX = o1.posX == null || o2.posX == null ? null : o1.posX * w1 + o2.posX * w2;
                        speedLimit.posY = o1.posY == null || o2.posY == null ? null : o1.posY * w1 + o2.posY * w2;
                        speedLimit.posZ = o1.posZ == null || o2.posZ == null ? null : o1.posZ * w1 + o2.posZ * w2;
                        sample.elements.push(speedLimit);
                    }
                    break;
                case TrafficElementType.TrafficSign:
                    {
                        var trafficSign = new TrafficSign(id, rawID, TrafficSignType.Stop);
                        trafficSign.signType = w1 > w2 ? o1.signType : w2.signType;
                        trafficSign.posX = o1.posX == null || o2.posX == null ? null : o1.posX * w1 + o2.posX * w2;
                        trafficSign.posY = o1.posY == null || o2.posY == null ? null : o1.posY * w1 + o2.posY * w2;
                        trafficSign.posZ = o1.posZ == null || o2.posZ == null ? null : o1.posZ * w1 + o2.posZ * w2;
                        sample.elements.push(trafficSign);
                    }
                    break;
                case TrafficElementType.TrafficLight:
                    {
                        var trafficLight = new TrafficLight(id, rawID);
                        trafficLight.state = w1 > w2 ? o1.state : o2.state;
                        trafficLight.arrows = w1 > w2 ? o1.arrows : o2.arrows;
                        trafficLight.posX = o1.posX == null || o2.posX == null ? null : o1.posX * w1 + o2.posX * w2;
                        trafficLight.posY = o1.posY == null || o2.posY == null ? null : o1.posY * w1 + o2.posY * w2;
                        trafficLight.posZ = o1.posZ == null || o2.posZ == null ? null : o1.posZ * w1 + o2.posZ * w2;
                        trafficLight.stopPosX = o1.stopPosX == null || o2.stopPosX == null ? null : o1.stopPosX * w1 + o2.stopPosX * w2;
                        trafficLight.stopPosY = o1.stopPosY == null || o2.stopPosY == null ? null : o1.stopPosY * w1 + o2.stopPosY * w2;
                        sample.elements.push(trafficLight);
                    }
                    break;
                case TrafficElementType.StopLine:
                    {
                        var stopLine = new StopLine(id, rawID);
                        stopLine.distance = o1.distance * w1 + o2.distance * w2;
                        stopLine.heading = o1.heading == null || o2.heading == null ? null : o1.heading * w1 + o2.heading * w2;
                        sample.elements.push(stopLine);
                    }
                    break;
                case TrafficElementType.SpeedBump:
                    {
                        var speedBump = new SpeedBump(id, rawID);
                        speedBump.distance = o1.distance * w1 + o2.distance * w2;
                        speedBump.heading = o1.heading == null || o2.heading == null ? null : o1.heading * w1 + o2.heading * w2;
                        sample.elements.push(speedBump);
                    }
                    break;
                case TrafficElementType.ArrowMark:
                    {
                        var arrowMark = new ArrowMark(id, rawID);
                        arrowMark.arrows = w1 > w2 ? o1.arrows : o2.arrows;
                        arrowMark.posX = o1.posX == null || o2.posX == null ? null : o1.posX * w1 + o2.posX * w2;
                        arrowMark.posY = o1.posY == null || o2.posY == null ? null : o1.posY * w1 + o2.posY * w2;
                        sample.elements.push(arrowMark);
                    }
                    break;
                case TrafficElementType.ProhibitionZone:
                    {
                        var prohibitionZone = new ProhibitionZone(id, rawID);
                        prohibitionZone.centerX = o1.centerX * w1 + o2.centerX * w2;
                        prohibitionZone.centerY = o1.centerY * w1 + o2.centerY * w2;
                        prohibitionZone.width = o1.width * w1 + o2.width * w2;
                        prohibitionZone.length = o1.length * w1 + o2.length * w2;
                        prohibitionZone.heading = o1.heading == null || o2.heading == null ? null : o1.heading * w1 + o2.heading * w2;
                        sample.elements.push(prohibitionZone);
                    }
                    break;
                case TrafficElementType.ParkingSlot:
                    {
                        var parkingSlot = new ParkingSlot(id, rawID);
                        parkingSlot.centerX = o1.centerX * w1 + o2.centerX * w2;
                        parkingSlot.centerY = o1.centerY * w1 + o2.centerY * w2;
                        parkingSlot.width = o1.width * w1 + o2.width * w2;
                        parkingSlot.length = o1.length * w1 + o2.length * w2;
                        parkingSlot.heading = o1.heading == null || o2.heading == null ? null : o1.heading * w1 + o2.heading * w2;
                        parkingSlot.occupancy = w1 > w2 ? o1.occupancy : o2.occupancy;
                        sample.elements.push(parkingSlot);
                    }
                    break;
                case TrafficElementType.ZebraCrossing:
                    {
                        var zebraCrossing = new ZebraCrossing(id, rawID);
                        zebraCrossing.nearDistance = o1.nearDistance * w1 + o2.nearDistance * w2;
                        zebraCrossing.farDistance = o1.farDistance == null || o2.farDistance == null ? null : o1.farDistance * w1 + o2.farDistance * w2;
                        zebraCrossing.heading = o1.heading == null || o2.heading == null ? null : o1.heading * w1 + o2.heading * w2;
                        sample.elements.push(zebraCrossing);
                    }
                    break;
            }
        }
    }

    return sample;
}

// Supported protocols / 交通信息样本支持解析的协议
var trafficSampleProtocols = [
    "traffic-sample-v1",
    "traffic-sample-v2",
];

// Converters / 通用样本转换函数表
var trafficSampleConverters = {
    "traffic-sample-v1": convTrafficSampleV1,
    "traffic-sample-v2": convTrafficSampleV2,
};

// Use this function to query traffic info sample at target time. If "interpolated" is true, the interpolated frame is preferred, otherwise the nearest frame / 获取指定session指定时刻的交通信息样本，若interpolated为true则优先取插值帧，否则固定为最近帧
function getTrafficSample(channel, session, time, interpolated) {
    var s1 = null, w1 = null, s2 = null, w2 = null;
    var pairV2 = biGetGeneralSamplePair('traffic-sample-v2', channel, session, time, 1);
    var pairV1 = biGetGeneralSamplePair('traffic-sample-v1', channel, session, time, 1);
    if (pairV2 != null) {
        if (pairV2.sample1 != null) s1 = convTrafficSampleV2(pairV2.sample1);
        if (pairV2.sample2 != null) s2 = convTrafficSampleV2(pairV2.sample2);
        w1 = pairV2.weight1;
        w2 = pairV2.weight2;
    }
    else if (pairV1 != null) {
        if (pairV1.sample1 != null) s1 = convTrafficSampleV1(pairV1.sample1);
        if (pairV1.sample2 != null) s2 = convTrafficSampleV1(pairV1.sample2);
        w1 = pairV1.weight1;
        w2 = pairV1.weight2;
    }
    else return null;
    if (s1 == null || s2 == null) {
        return s1 == null ? s2 : s1;
    }
    if (interpolated) {
        return interpolateTrafficSample(session, time, s1, w1, s2, w2);
    }
    else {
        return w1 > w2 ? s1 : s2;
    }
}